<!--
*@Author: QWP
*@Description: 聊天框
*@Params: messageType  1 文本消息   2 文件消息  3 媒体消息   4 系统通知消息  5 撤回回推  6 已读回推   7 上下线推送  
*@Date: 2024-07-11 15:11:40
-->
<template>
  <a-layout class="f-h-100 u-chat-content s-bg-white">
    <a-layout-content style="position: relative; overflow: auto;"
      @keydown.enter.native="sendMsg('1')">
      <DynamicScroller
        :items="messages"
        :min-item-size="96"
        key-field="keyid"
        ref="msgBoxRef"
        class="f-h-100 f-padding-8"
        id="viturlBox"
        v-if="refreshContent"
        >
        <template v-slot="{ item, index, active }">
          <DynamicScrollerItem
            :item="item"
            :active="active"
            :size-dependencies="[
              item.messageInfo,
              item.fileUrl,
            ]"
            :data-index="index"
          >
            <!-- 撤回 -->
            <template v-if="item.messageType == '1' && item.status == 3">
              <RecallMsg :data="item" @click="(msg) => message = msg"/>
            </template>
            
            <template v-else>
              <div class="f-flex-column f-w-100 f-padding-tb8"
                :style="{'align-items': getPosition(item) == 'end' ? 'flex-end' : 'start'}">
                <div class="f-flex-column"
                  :style="{'align-items': getPosition(item) == 'end' ? 'flex-end' : 'start'}">
                  <span class="u-user-name">{{ item.nickname }}</span> 
                  <span class="u-user-time">{{ item.createtime }}</span>
                </div>
            
                <div class="f-flex-row u-all-msg-box" 
                  :style="{'flex-flow': getPosition(item) == 'start' ? 'row-reverse' : 'unset' }"
                  style="align-items: center">
                  <MsgMoreEdit :group="data"
                    :data="item"
                    :index="index"
                    @success="moreEditSuccess"/>
            
                  <ReadStatus :group="data" :data="item" :userList="userList"/>
                  
                  <!-- 文本 -->
                  <template v-if="item.messageType == '1' || item.messageType == '4'">
                    <div class="u-message-show-box"
                      :class="{'u-message-sys-box': item.messageType == 4}">
                      {{ item.messageInfo }}
                    </div>
                    <!-- 系统通知 -->
                    <a-row>
                      <FileMsgType :data="item" v-if="item['fileUrl'] && item.messageType == '4'" />
                    </a-row>
                  </template>
            
                  <!-- 文件 -->
                  <template v-else-if="item.messageType == '2'">
                    <a-row :justify="item.charPosition == 'right' ? 'end' : 'start'" >
                      <FileMsgType :data="item" v-if="item['fileUrl']" />
                    </a-row>
                  </template>
            
                  <!-- 图片,视频 -->
                  <template v-else-if="item.messageType == '3'">
                    <a-row :justify="item.charPosition == 'right' ? 'end' : 'start'" >
                      <template v-if="isVideo(item['fileUrl'])">
                        <video width="320" height="175" controls class="u-video-box">
                          <source :src="formatImgUrl(item['fileUrl'])" type="video/mp4">
                          您的浏览器不支持 视频播放。
                      </video>
                      </template>
                      <template  v-else>
                        <a-image :src="formatImgUrl(item['fileUrl'])" 
                          style="max-width: 150px;border-radius: 4px;">
                        </a-image>
                      </template>
                    </a-row>
                  </template>
            
                </div>
              </div>
            </template>
          </DynamicScrollerItem>
        </template>
      </DynamicScroller>
    </a-layout-content>

    <a-layout-footer class="u-chat-footer" v-if="data['types'] != 3">
      <a-row class="u-chat-tool">
        <a-space size="middle">
          <!-- <SmileOutlined class="u-icon"/> -->
          <u-field-upload v-model:value="fileList"
            :accept="setAccepFile"
            ref="uploadRef"
            :showBtn="false"
            :showList="false"
            :fileParams="{biztype: '2', bucket: 'EMERGENCY_BIZ_TYPE_CHATTINGRECORDS',maxCount: 1}"
            @success="sendMsg(uploadType)">
            <a-space size="middle" align="end">
              <FolderOpenOutlined class="u-icon" @click="uploadType = '2'"/>
              <PictureOutlined class="u-icon" @click="uploadType = '3'" />
            </a-space>
          </u-field-upload>
        </a-space>
      </a-row>
      <a-textarea v-model:value="message" 
        :auto-size="false"
        class="u-message-area">
      </a-textarea>
      <a-row justify="end" align="middle" class="f-padding-rl10" style="height: 36px"> 
        <a-button type="default" :loading="sendLoading" @click="sendMsg('1')">发送</a-button>
      </a-row>
    </a-layout-footer>
  </a-layout>
</template>

<script lang='ts' setup>
import { ref, reactive, computed, onMounted, watch, nextTick, onUnmounted, onActivated } from 'vue' 
import { useLoginStore, useChatStore } from '@/store'
import { storeToRefs } from 'pinia'
import { urlFile, alertC, messageC, numberC } from '@/util/common'
import { api, imgApi } from '@/http/api'
import * as chatApi from '@/service/emergency/chat'
import { RecycleScroller, DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { throttle, formatImgUrl } from '@/util/format.ts'
import dayjs from 'dayjs'
import FileMsgType from './FileMsgType.vue'
import RecallMsg from './component/RecallMsg.vue'
import MsgMoreEdit from './component/MsgMoreEdit.vue'
import ReadStatus from './component/ReadStatus.vue'
import { chatEventBus } from '@/util/eventBus'

const props = defineProps<{
  data: object,
  dailogOpen: boolean
}>()

const { userInfo } = storeToRefs(useLoginStore())
const chatStore = useChatStore()
const { recallMsgs } = storeToRefs(chatStore)

const msgBoxRef = ref()

// 1   文本     2 文件      3 图片
const message = ref<string>("")
const uploadRef = ref()
const uploadType = ref<string>('')
const fileList = ref<any[]>([])

const setAccepFile = computed(() =>  {
  const fileType = {
    3: '.png,.jpg,.jpeg,.gif,.mp4',
    2: '.doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .pdf'
  }
  return fileType[uploadType.value] || ''
})

const loading = ref<boolean>(false)
const messages = ref<any[]>([])

const msgTotal = ref<number>(0)
const searchF = ref({
  current: 1,
  model: {},
  pageSize: 200
})

// 群聊初次进入时加载历史信息
const refreshContent = ref<boolean>(true)
const userList = ref<any[]>([])
const isInitLoad = ref<boolean>(true)
const positionTo = ref<number>(-1)
const isLoadAll = ref<boolean>(false)
const initChatMsg = async (current?: number) => {
  loading.value = true;
  try {
    searchF.value.current = current ? current : 1;
    searchF.value.size = searchF.value.pageSize;

    searchF.value.model = {
      type: props.data['types'] , // 1 群聊  2 私聊  3 系统通知
      keyId: props.data['keyid']
    }

    const res = await chatApi.openMessage(searchF.value)
    if(res.code == 0) {
      const records = (res.data.chatRecords.records || []).reverse()
      messages.value = [...records, ...messages.value];

      msgTotal.value = numberC(res.data.chatRecords.total)

      // 初次进入
      if(!current) {
        userList.value = res.data.userList || []
        // 侧边显示群聊人员
        chatEventBus.emit('userList', userList.value)
        scrollBottom()
        setTimeout(() => isInitLoad.value = false, 500)
      }
      else {
        positionTo.value = records.length;
        if(records.length == 0) {
          isLoadAll.value = true
        }
      }
    }
  } catch (error) {}
  loading.value = false;
}

// 消息的更多操作成功的回调函数
const moreEditSuccess = ({ type, data, index }) => {
  if(type == 'recall') {
    // 删除数据
    messages.value.splice(index, 1);
    getOtherRecallMsg(data)
    scrollBottom()
  }
}

// 发送消息
const sendLoading = ref<boolean>(false)
const sendMsg = async (msgType: string) => {
  if(!message.value && fileList.value.length == 0) {
    messageC("不能发送空白消息!");
    return
  }

  sendLoading.value = true;
  try {
    let file = {};
    if(fileList.value.length != 0) {
      file = fileList.value[0]
    }
    const form = {
      type: props.data['types'],
      messageType: msgType, // 1 文本    2 文件   3 图片视频
      fileName: file['filename'], // 文件名称
      fileUrl: file['filepath'], // 文件路径
      messageInfo: message.value, // 文本信息
      logUserId: userInfo.value['userId'], // 用户id
      appendixSaveVOList: file
    }

    // chatEventBus.emit('sendMsg', form)

    // 群聊
    if(form.type == '1') {
      form.groupId = props.data['keyid'];
    }
    // 私聊
    else if(form.type == '2') {
      form.fkUser = props.data['keyid'];
    }

    const res = await chatApi.sendMessage(form)

    if(res.code == 0) {
      message.value = ''
      const msg = {
        ...(res.data || {}),
        time: res.data['createtime'] || '',
      }
      messages.value.push(msg)
      updateGoupMsg({
        ...msg,
        time: msg['createtime'],
      })

      msgTotal.value += 1;
      scrollBottom()

    }
  } catch (error) {}
  fileList.value = []
  sendLoading.value = false;
}

// 获取实时消息
const getRealTimeMsg = () => {
  chatEventBus.on('allRealMsg', (data) => {
    if(data) {
      // 正常消息推送，已读消息推送
      const chattingDto = data['chattingDto'] || {}

      // 撤回消息推送
      const sourceMessage = JSON.parse(data['sourceMessage'] || "{}")

      // 撤回消息推送
      if(data['sourceMessage']) {
        if(sourceMessage['messageType'] == 5) {
          getOtherRecallMsg(data)
          return
        }
      }

      // 已读回推
      if(chattingDto['messageType'] == 6) {
        let index = messages.value.findIndex(item => item.keyid == chattingDto['keyid']);
        if(index != -1) {
          let tData = messages.value[index];
          let readBy = tData['readBy'] || [];
          let cReadBy = (chattingDto['readBy'] || '').replace(/\[*\]*/g, '').split(',');
          let readByFinel = Array.from(new Set([...readBy, ...cReadBy]))
          messages.value.splice(index, 1, { ...tData, readBy: readByFinel})
        }
        return
      }

      // 正常消息接收
      let messageO = data || {};

      const typeId = { 1: 'groupId', 2: 'sender' }
      const { sender, message, chatType } = messageO

      let tId = messageO[typeId[chatType]]
      if(!sender || tId != props.data['keyid']) {
        return
      }

      updateRead(tId, chatType, data['chattingDto'] || {})
      if(sender != userInfo.value['userId']) {
        const tMsg = {
          charPosition: sender == userInfo.value['userId'] ? "right" : "left",
          messageInfo: message,
          createtime: messageO['sendTime'],
          time: messageO['sendTime'],
          keyid: messageO['messageId'],
          ...messageO,
        }
        messages.value.push(tMsg)

        msgTotal.value += 1;

        updateGoupMsg(tMsg)

        scrollBottom()
      }
    }
  })
}

// 渲染撤回消息
const viewRcallIds: string[] = []
const getOtherRecallMsg = (data) => {
  let tMsg = JSON.parse(data['sourceMessage'] || '{}');
  let message = tMsg['message'] || {};

  if(viewRcallIds.includes(message['keyid'])) {
    return
  }

  let index = messages.value.findIndex(i => i.keyid == data['messageId']);
  if(index != -1) {
    messages.value.splice(index, 1, {
      ...message,
      messageInfo: data['message'] || '',
      status: 3,
      messageType: 1,
      createtime: dayjs().format('YYYY-MM-DD HH:mm:ss')
    })
  }

  viewRcallIds.push(message['keyid'])

  scrollBottom()
}

// 更新当前消息为已读
const updateRead = async (chatId, chatType, chattingDto) => {
  if(props.dailogOpen == false) {
    return
  }
  try {
    const form = {
      chatId,
      chatType,
      chattingDto
    }
    const res = await chatApi.read(form)
  } catch (error) {}
}

// 滚动条到最底部
const scrollBottom = (index?: number) =>  {
  nextTick(() => {
    if(msgBoxRef.value) {
      msgBoxRef.value.scrollToBottom()
    }
  })
}

// 滚动加载
let previous = 0;
const scrollLoad = (isInit?: boolean) => {
  const el = document.getElementById("viturlBox");
  el.addEventListener('scroll', async function(e) {
    let now = new Date()
    if(e.target.scrollTop == 0 && 
      isLoadAll.value == false && 
      now - previous > 500 && 
      messages.value.length != 0) {
      if(messages.value.length >= msgTotal.value) {
        return
      }
      await initChatMsg(searchF.value.current += 1)
      msgBoxRef.value.scrollToItem(positionTo.value)
      previous = now;
    }
  })
  el.addEventListener('whell', function(e) {
    if(loading.value == true) {
      e.preventDefault()
    }
  }, { passive: false })
}

// 判断是否是视频
const isVideo = (url: string) => {
  let types = url.split('.');
  return types[types.length - 1].toLocaleLowerCase() == 'mp4'
}

// 获取显示位置
const getPosition = (item) => {
  if(item.messageType == '4') {
    return 'start'
  }
  return item.charPosition == 'right' ? 'end' : 'start'
}

// 更新群列表的最新消息展示
const updateGoupMsg = (msg) => {
  chatEventBus.emit('updateGoupMsg', { keyid: props.data['keyid'], msg })
}

onMounted(() => {

  getRealTimeMsg()
  
  scrollLoad(true)
})

watch(() => props.data, (nval, ovla) => {
  messages.value = []
  message.value = ""
  isInitLoad.value = true
  positionTo.value = -1
  if(Object.values(props.data || {}).length != 0) {
    initChatMsg()
  }
}, { immediate: true, deep: true })
</script>

<style lang='scss'>
.u-chat-content {
  .u-user-name {
    font-family: PingFang SC, PingFang SC;
    font-weight: 400;
    font-size: 16px;
    color: #000000;
    line-height: 18px;
  }
  .u-user-time {
    color: #999;
  }
  .u-all-msg-box {
    width: max-content;
    max-width: 80%;
  }
  .u-message-show-box {
    max-width: 100%;
    width: max-content;
    padding: 10px;
    background: #E4E7EB;
    border-radius: 4px 4px 4px 4px;
  }
 
  .u-video-box {
    border-radius: 6px;
    box-shadow: 0 0 0 2px rgba(0, 0, 0, .05);
  }
  .u-chat-footer {
    height: 200px; 
    padding: 0; 
    background-color: #fff;
    border-top: 1px solid rgba(0,0,0,0.16);
    .u-chat-tool {
      padding: 10px;
      .u-icon {
        cursor: pointer;
        font-size: 22px;
        color: #666
      }
    }
    .u-message-area {
      height: calc(100% - 82px);
      resize: none
    }
  }
  .ant-upload-wrapper {
    display: flex;
    flex-direction: row;
    align-items: center
  }
}
</style>